package org.elsfs.cloud.common.mybatis.ext.sql.support;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Sets;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.util.validation.*;
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
import net.sf.jsqlparser.util.validation.feature.MySqlVersion;
import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
import org.elsfs.cloud.common.util.lang.ArrayUtils;
import org.elsfs.cloud.common.util.lang.StringUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * SQL工具类

 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SqlUtils {

    /**
     * 查询SQL特性集
     */
    private static final Set<ValidationCapability> QUERY_SQL_FEATURES = Sets.newHashSet(
        MySqlVersion.V8_0,
        PostgresqlVersion.V12,
        FeaturesAllowed.SELECT.copy().add(
            Feature.jdbcParameter,
            Feature.jdbcNamedParameter
        )
    );

    /**
     * 处理SQL参数值
     *
     * @param value 参数值
     * @return 处理后的参数
     */
    public static String handleSqlParameterValue(Object value) {
        if (value instanceof CharSequence) {
            return  "'" + value +  "'";
        } else if (value instanceof Enum<?> enumeration) {
            return  "'" + enumeration.name() +  "'";
        } else if (value instanceof LocalDateTime || value instanceof LocalDate || value instanceof LocalTime) {
            return  "'" + value +  "'";
        } else if (value == null) {
            return "NULL";
        } else {
            return StringUtils.str(value, StandardCharsets.UTF_8);
        }
    }

    /**
     * 处理SQL表达式
     *
     * @param sqlExpression SQL表达式
     * @param args SQL表达式参数
     * @return 处理参数后的SQL表达式
     */
    public static String handleSqlExpression(String sqlExpression, Object... args) {
        if (StringUtils.isBlank(sqlExpression) || ArrayUtils.isEmpty(args)) {
            return sqlExpression;
        }

        Object[] processedArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            processedArgs[i] = handleSqlParameterValue(args[i]);
        }

        return String.format(
            sqlExpression,
            processedArgs
        );
    }

    /**
     * 解析条件SQL列
     *
     * @param conditionSql 条件SQL
     * @return 条件列集合
     */
    public static Set<Column> parseConditionSqlColumns(String conditionSql) {
        try {
            Expression expression = CCJSqlParserUtil.parseCondExpression(conditionSql);
            WhereConditionExpressionVisitor expressionVisitor = new WhereConditionExpressionVisitor();
            expression.accept(expressionVisitor);

            return expressionVisitor.columns;
        } catch (JSQLParserException e) {
            throw new MybatisPlusException("解析条件SQL条件列出现异常："+e.getMessage(),e );
        }
    }

    /**
     * 解析条件SQL列名
     *
     * @param conditionSql 条件SQL
     * @return 条件列名集合
     */
    public static Set<String> parseConditionSqlColumnNames(String conditionSql) {
        return parseConditionSqlColumns(conditionSql)
            .stream()
            .map(Column::getFullyQualifiedName)
            .collect(Collectors.toSet());
    }

    /**
     * 验证查询SQL
     *
     * @param sql      SQL语句
     * @param errorTip 错误提示
     */
    public static void assertIsQuerySql(String sql, String errorTip) {
        List<ValidationError> validationErrors = Validation.validate(
            QUERY_SQL_FEATURES,
            sql
        );

        for (ValidationError validationError : validationErrors) {
            for (ValidationException error : validationError.getErrors()) {
                if (error instanceof ParseException) {
                    // 语法错误
                    LOGGER.warn("{}SQL【{}】存在语法错误", errorTip, sql, error);
                    throw new MybatisPlusException(error+"SQL存在语法错误"+errorTip);
                } else {
                    LOGGER.warn("{}SQL【{}】不受允许：", errorTip, sql, error);
                    throw new MybatisPlusException("不允许的{}SQL：" +errorTip+ error.getMessage());
                }
            }
        }
    }

    /**
     * 条件表达式访问器
     */
    static class WhereConditionExpressionVisitor extends ExpressionVisitorAdapter {

        /**
         * 条件字段列名
         */
        private final Set<Column> columns = Sets.newHashSet();

        /**
         * 访问函数表达式
         *
         * @param function 函数表达式
         */
        @Override
        public void visit(Function function) {
            ExpressionList parameters = function.getParameters();
            if (parameters == null) {
                return;
            }

            for (Expression expression : parameters.getExpressions()) {
                expression.accept(this);
            }
        }

        /**
         * 方法圆括号表达式
         *
         * @param parenthesis 圆括号表达式
         */
        @Override
        public void visit(Parenthesis parenthesis) {
            parenthesis.getExpression().accept(this);
        }

        /**
         * 访问And表达式
         *
         * @param expr And表达式
         */
        @Override
        public void visit(AndExpression expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问Or表达式
         *
         * @param expr Or表达式
         */
        @Override
        public void visit(OrExpression expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问Xor表达式
         *
         * @param expr Xor表达式
         */
        @Override
        public void visit(XorExpression expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问Between表达式
         *
         * @param expr Between表达式
         */
        @Override
        public void visit(Between expr) {
            expr.getLeftExpression().accept(this);
            expr.getBetweenExpressionStart().accept(this);
            expr.getBetweenExpressionEnd().accept(this);
        }

        /**
         * 访问等于表达式
         *
         * @param expr 等于表达式
         */
        @Override
        public void visit(EqualsTo expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问大于表达式
         *
         * @param expr 大于表达式
         */
        @Override
        public void visit(GreaterThan expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问大于等于表达式
         *
         * @param expr 大于等于表达式
         */
        @Override
        public void visit(GreaterThanEquals expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问In表达式
         *
         * @param expr In表达式
         */
        @Override
        public void visit(InExpression expr) {
            expr.getLeftExpression().accept(this);
            if (expr.getRightItemsList() != null) {
                expr.getRightItemsList().accept(this);
            } else if (expr.getRightExpression() != null) {
                expr.getRightExpression().accept(this);
            }
        }

        /**
         * 访问为空表达式
         *
         * @param expr 为空表达式
         */
        @Override
        public void visit(IsNullExpression expr) {
            expr.getLeftExpression().accept(this);
        }

        /**
         * 访问模糊匹配表达式
         *
         * @param expr 模糊匹配表达式
         */
        @Override
        public void visit(LikeExpression expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问小于表达式
         *
         * @param expr 小于表达式
         */
        @Override
        public void visit(MinorThan expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问小于等于表达式
         *
         * @param expr 小于等于表达式
         */
        @Override
        public void visit(MinorThanEquals expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问不等于表达式
         *
         * @param expr 不等于表达式
         */
        @Override
        public void visit(NotEqualsTo expr) {
            expr.getLeftExpression().accept(this);
            expr.getRightExpression().accept(this);
        }

        /**
         * 访问列表达式
         *
         * @param column 列表达式
         */
        @Override
        public void visit(Column column) {
            this.columns.add(column);
        }

        /**
         * 访问数据类型转换表达式
         *
         * @param expr 数据类型转换表达式
         */
        @Override
        public void visit(CastExpression expr) {
            expr.getLeftExpression().accept(this);
        }

        /**
         * 访问取反表达式
         *
         * @param notExpr 取反表达式
         */
        @Override
        public void visit(NotExpression notExpr) {
            notExpr.getExpression().accept(this);
        }

        /**
         * 访问符号表达式
         *
         * @param expr 符号表达式
         */
        @Override
        public void visit(SignedExpression expr) {
            expr.getExpression().accept(this);
        }
    }
}
